#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pd_linux.h"
#include "config_pl2303.h"
#include "xlibusb.h"

namespace config_pl2303
{
    const int PL2303_REQTYPE_HOST2DEVICE_VENDOR = 0x40;
    const int PL2303_REQTYPE_DEVICE2HOST_VENDOR = 0xC0;
    const int PL2303_REQTYPE_HOST2DEVICE = 0x21;
    const int PL2303_REQTYPE_DEVICE2HOST = 0xA1;

    const int PL2303_VENDOR_WRITE_REQUEST = 0x01;
    const int PL2303_SET_LINE_CODING = 0x20;
    const int PL2303_GET_LINE_CODING = 0x21;
    const int PL2303_SET_CONTROL_REQUEST = 0x22;

    const int DATA_BITS_5 = 5;
    const int DATA_BITS_6 = 6;
    const int DATA_BITS_7 = 7;
    const int DATA_BITS_8 = 8;

    const int STOP_BITS_1 = 1;
    const int STOP_BITS_15 = 3;
    const int STOP_BITS_2 = 2;

    const int PARITY_NONE = 0;
    const int PARITY_ODD = 1;
    const int PARITY_EVEN = 2;
    const int PARITY_MARK = 3;
    const int PARITY_SPACE = 4;

    const int FLOW_CONTROL_OFF = 0;
    const int FLOW_CONTROL_RTS_CTS = 1;
    const int FLOW_CONTROL_DSR_DTR = 2;
    const int FLOW_CONTROL_XON_XOFF = 3;

    unsigned char defaultSetLine[]
    {
        (unsigned char) 0x80, // [0:3] Baud rate (reverse hex encoding 9600:00 00 25 80 -> 80 25 00 00)
        (unsigned char) 0x25,
        (unsigned char) 0x00,
        (unsigned char) 0x00,
        (unsigned char) 0x00, // [4] Stop Bits (0=1, 1=1.5, 2=2)
        (unsigned char) 0x00, // [5] Parity (0=NONE 1=ODD 2=EVEN 3=MARK 4=SPACE)
        (unsigned char) 0x08  // [6] Data Bits (5=5, 6=6, 7=7, 8=8)
    };

    int getLineCoding(void *handle, unsigned char *data, int length)
    {
        int response = x_libusb_controltransfer(handle, PL2303_REQTYPE_DEVICE2HOST, PL2303_GET_LINE_CODING, 0, 0, data, length, 1000);
        return response;
    }

    int setControlCommand(void *handle, int reqType, int request, int value, int index, unsigned char *data, int dataLength)
    {
        int response = x_libusb_controltransfer(handle, reqType, request, value, index, data, dataLength, 1000);
        return response;
    }

    int  Init(void *handle)
    {
        //Default Setup
        unsigned char buf[1] = {0x0};
        //Specific vendor stuff that I barely understand but It is on linux drivers, So I trust :)
        if(setControlCommand(handle, PL2303_REQTYPE_DEVICE2HOST_VENDOR, PL2303_VENDOR_WRITE_REQUEST, 0x8484, 0, buf, 1) < 0)
        {
            return -1;
        }
        if(setControlCommand(handle, PL2303_REQTYPE_HOST2DEVICE_VENDOR, PL2303_VENDOR_WRITE_REQUEST, 0x0404, 0, NULL, 0) < 0)
        {
            return -1;
        }
        if(setControlCommand(handle, PL2303_REQTYPE_DEVICE2HOST_VENDOR, PL2303_VENDOR_WRITE_REQUEST, 0x8484, 0, buf, 1) < 0)
        {
            return -1;
        }
        if(setControlCommand(handle, PL2303_REQTYPE_DEVICE2HOST_VENDOR, PL2303_VENDOR_WRITE_REQUEST, 0x8383, 0, buf, 1) < 0)
        {
            return -1;
        }
        if(setControlCommand(handle, PL2303_REQTYPE_DEVICE2HOST_VENDOR, PL2303_VENDOR_WRITE_REQUEST, 0x8484, 0, buf, 1) < 0)
        {
            return -1;
        }
        if(setControlCommand(handle, PL2303_REQTYPE_HOST2DEVICE_VENDOR, PL2303_VENDOR_WRITE_REQUEST, 0x0404, 1, NULL, 0) < 0)
        {
            return -1;
        }
        if(setControlCommand(handle, PL2303_REQTYPE_DEVICE2HOST_VENDOR, PL2303_VENDOR_WRITE_REQUEST, 0x8484, 0, buf, 1) < 0)
        {
            return -1;
        }
        if(setControlCommand(handle, PL2303_REQTYPE_DEVICE2HOST_VENDOR, PL2303_VENDOR_WRITE_REQUEST, 0x8383, 0, buf, 1) < 0)
        {
            return -1;
        }
        if(setControlCommand(handle, PL2303_REQTYPE_HOST2DEVICE_VENDOR, PL2303_VENDOR_WRITE_REQUEST, 0x0000, 1, NULL, 0) < 0)
        {
            return -1;
        }
        if(setControlCommand(handle, PL2303_REQTYPE_HOST2DEVICE_VENDOR, PL2303_VENDOR_WRITE_REQUEST, 0x0001, 0, NULL, 0) < 0)
        {
            return -1;
        }
        if(setControlCommand(handle, PL2303_REQTYPE_HOST2DEVICE_VENDOR, PL2303_VENDOR_WRITE_REQUEST, 0x0002, 0x0044, NULL, 0) < 0)
        {
            return -1;
        }
        // End of specific vendor stuff
        if(setControlCommand(handle, PL2303_REQTYPE_HOST2DEVICE, PL2303_SET_CONTROL_REQUEST, 0x0003, 0, NULL, 0) < 0)
        {
            return -1;
        }
        if(setControlCommand(handle, PL2303_REQTYPE_HOST2DEVICE, PL2303_SET_LINE_CODING, 0x0000, 0, defaultSetLine, 7) < 0)
        {
            return -1;
        }
        if(setControlCommand(handle, PL2303_REQTYPE_HOST2DEVICE_VENDOR, PL2303_VENDOR_WRITE_REQUEST, 0x0505, 0x1311, NULL, 0) < 0)
        {
            return -1;
        }
        return 0;
    }

    void  Free(void *handle)
    {
        //resverd
    }

    void SetBaudRate(void *handle, int baudRate)
    {
        unsigned char data[7] = {0x0};
        getLineCoding(handle, data, 7);
        data[0] = (unsigned char)(baudRate & 0xff);
        data[1] = (unsigned char)(baudRate >> 8 & 0xff);
        data[2] = (unsigned char)(baudRate >> 16 & 0xff);
        data[3] = (unsigned char)(baudRate >> 24 & 0xff);
        setControlCommand(handle, PL2303_REQTYPE_HOST2DEVICE, PL2303_SET_LINE_CODING, 0x0000, 0, data, 7);
    }

    void SetParity(void *handle, int parity)
    {
        unsigned char data[7] = {0x0};
        getLineCoding(handle, data, 7);
        switch(parity)
        {
            case PARITY_NONE:
            {
                data[5] = 0x00;
                setControlCommand(handle, PL2303_REQTYPE_HOST2DEVICE, PL2303_SET_LINE_CODING, 0x0000, 0, data, 7);
            }
            break;
            case PARITY_ODD:
            {
                data[5] = 0x01;
                setControlCommand(handle, PL2303_REQTYPE_HOST2DEVICE, PL2303_SET_LINE_CODING, 0x0000, 0, data, 7);
            }
            break;
            case PARITY_EVEN:
            {
                data[5] = 0x02;
                setControlCommand(handle, PL2303_REQTYPE_HOST2DEVICE, PL2303_SET_LINE_CODING, 0x0000, 0, data, 7);
            }
            break;
            case PARITY_MARK:
            {
                data[5] = 0x03;
                setControlCommand(handle, PL2303_REQTYPE_HOST2DEVICE, PL2303_SET_LINE_CODING, 0x0000, 0, data, 7);
            }
            break;
            case PARITY_SPACE:
            {
                data[5] = 0x04;
                setControlCommand(handle, PL2303_REQTYPE_HOST2DEVICE, PL2303_SET_LINE_CODING, 0x0000, 0, data, 7);
            }
            break;
            default:
                return;
        }
    }

    void SetFlowControl(void *handle, int flowControl)
    {
        //resverd
    }

    void SetDataBits(void *handle, int dataBits)
    {
        unsigned char data[7] = {0x0};
        getLineCoding(handle, data, 7);
        switch(dataBits)
        {
            case DATA_BITS_5:
            {
                data[6] = 0x05;
                setControlCommand(handle, PL2303_REQTYPE_HOST2DEVICE, PL2303_SET_LINE_CODING, 0x0000, 0, data, 7);
            }
            break;
            case DATA_BITS_6:
            {
                data[6] = 0x06;
                setControlCommand(handle, PL2303_REQTYPE_HOST2DEVICE, PL2303_SET_LINE_CODING, 0x0000, 0, data, 7);
            }
            break;
            case DATA_BITS_7:
            {
                data[6] = 0x07;
                setControlCommand(handle, PL2303_REQTYPE_HOST2DEVICE, PL2303_SET_LINE_CODING, 0x0000, 0, data, 7);
            }
            break;
            case DATA_BITS_8:
            {
                data[6] = 0x08;
                setControlCommand(handle, PL2303_REQTYPE_HOST2DEVICE, PL2303_SET_LINE_CODING, 0x0000, 0, data, 7);
            }
            break;
            default:
                return;
        }
    }

    void SetStopBits(void *handle, int stopBits)
    {
        unsigned char data[7] = {0x0};
        getLineCoding(handle, data, 7);
        switch(stopBits)
        {
            case STOP_BITS_1:
            {
                data[4] = 0x00;
                setControlCommand(handle, PL2303_REQTYPE_HOST2DEVICE, PL2303_SET_LINE_CODING, 0x0000, 0, data, 7);
            }
            break;
            case STOP_BITS_15:
            {
                data[4] = 0x01;
                setControlCommand(handle, PL2303_REQTYPE_HOST2DEVICE, PL2303_SET_LINE_CODING, 0x0000, 0, data, 7);
            }
            break;
            case STOP_BITS_2:
            {
                data[4] = 0x02;
                setControlCommand(handle, PL2303_REQTYPE_HOST2DEVICE, PL2303_SET_LINE_CODING, 0x0000, 0, data, 7);
            }
            break;
            default:
                return;
        }
    }

    void SetBreak(void *handle, int state)
    {
        //resverd
    }
};

